
#ActiveProcess: A Workflow/BPM engine on Rails that is inspired by JBPM

#activeprocess.rb
#Author: liuxu
module ActiveProcess
  
  class GraphItem
    attr_accessor :id
    attr_accessor :name
    attr_accessor :description
    
    attr_accessor :events
    def initialize name
      @name = name
      @events = []
    end

    def fire_event(execution)
      @events.each do |event|
        event.execute(execution) if supports_event(event)
      end
    end
  end

  class Event
    PROCESS_START = :process_start
    PROCESS_STOP = :process_stop

    NODE_AFTER_ENTER = :node_after_enter
    NODE_BEFORE_LEAVE = :node_before_leave

    DIRECTION_TAKE = :direction_take

    attr_accessor :type
    attr_accessor :action
    def initialize(type, &action)
      @type = type
      @action = action
    end
    def execute(execution)
      action.call(execution) if action
    end
  end

	class ProcessDef
    attr_accessor :name
    attr_accessor :nodes
    attr_accessor :directions
    attr_accessor :start_node

    def initialize name
      @name = name
      @nodes = []
      @directions = []
    end

    def start_process auto_start = true
      process_inst = ProcessInst.new(self)
      process_inst.start if auto_start
      process_inst
    end

    def to_s
      "#{@name}: \n#{@nodes.join(", ")}\n#{@directions.join(", \n")}\n"
    end
	end

	class Node
    attr_accessor :name
    attr_accessor :enter_directions
    attr_accessor :leave_directions
    attr_accessor :action
    
    def initialize name, &action
      @name = name
      @action = action
      @enter_directions = []
      @leave_directions = []
    end

	  def enter execution
      execute execution
    end

	  def execute execution
      @action.call(execution) if @action
      leave execution
    end

    def leave(execution, direction=nil)
      if direction
        direction.take execution
	    else
        default_direction(execution).take execution
      end
    end

    def default_direction(execution)
      @leave_directions[0]
    end
    def to_s
      @name
    end
  end
  
  class Direction
    attr_accessor :name
    attr_accessor :condition

    attr_accessor :from_node
    attr_accessor :to_node

    def initialize from, to, name = nil
      @from_node = from
      @to_node = to
      @name = name ? name : "#{to.name}"
      @from_node.leave_directions << self
      @to_node.enter_directions << self
    end

    def take execution
      execution.node = @to_node
      execution.node_path << @to_node
      execution.direction_path << self
      @to_node.enter execution
    end

    def to_s
      "->[#{@name}]"
    end
  end

  class EndState < Node
    def execute execution
      execution.stop
    end
  end

  class State < Node
    def execute execution
      #do nothing
    end
  end

  class Fork < Node
    def execute execution
      chilren = {}
      for dir in @leave_directions
       chilren[dir] = execution.new_child(dir.name)
      end

      chilren.each do |dir, child|
        dir.take child
      end
    end
  end

  class Join < Node
    def execute execution
      execution.stop
      parent = execution.parent
      if parent.reactive?
        puts "Join:execute > parent.reactive true"
        leave parent
      end
    end
  end

  class Execution
    attr_accessor :process_inst
    attr_accessor :children
    attr_accessor :node
    attr_accessor :name

    attr_accessor :parent
    attr_accessor :children
    attr_accessor :reactive_parent

    attr_accessor :start_at
    attr_accessor :stop_at
    attr_accessor :node_path
    attr_accessor :direction_path
    attr_accessor :context
    def initialize(node, parent = nil, name = nil)
      @node = node
      @start_at = Time.new
      @node_path = [] << node
      @direction_path = []
      @context = {}
      if parent
        @parent = parent
        @name = parent.name + "/" + name
      else
        @name = "main"
      end
    end

    def new_child(name)
      child = Execution.new(@node, self, name)
      if @children == nil
        @children = []
      end
      @children << child
      child
    end

    def signal
      @node.leave self
    end

    def stop
      puts "#{@name} stop!"
      @stop_at = Time.new
      if @parent == nil # 主线程
        @process_inst.stop
      end
    end

    def living?
      @stop_at.nil?
    end

    def [] name, value
      @context[name] = value
    end

    def []= name
      value = @context[name.intern]
      if value
        value
      elsif @parent
        @parent[name.intern]
      end
    end

    def reactive?
      return false if @children.nil?
      has_aliving = false
      puts @children
      @children.each {|child| puts "#{child.name} -> #{child.stop_at}"; has_aliving = true if child.living?}
      not has_aliving
    end

    def to_s
      s =  @name + ": " + @node_path.join(" -> ")
      if @children
        @children.each{|child| s << "\n  " << child.to_s}
      end
      s
    end
  end
  
  class ProcessInst
    attr_accessor :process_def
    attr_accessor :main_execution

    attr_accessor :start_at
    attr_accessor :stop_at

    def initialize process_def
      @process_def = process_def
      @main_execution = Execution.new process_def.start_node
      @main_execution.process_inst = self
    end

    def start
      @start_at = Time.new
      @main_execution.signal
    end

    def node
      @main_execution.node
    end

    def signal
      @main_execution.signal
    end

    def stop
      puts "process stop!"
      @stop_at = Time.new
      if @main_execution.living?
        @main_execution.stop
      end
    end

    def stop?
      @stop_at != nil
    end

    def stop_at
      @stop_at
    end
  end

end
